#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/3/28 13:03
# @Author  : Grayson Liu
# @Email   : graysonliu@foxmail.com
# @File    : ParaExpForm.py
import copy
import json

from PyQt5.Qt import *

from CustomSignal import CustomSignal
from MethodList import MethodList
from ui.Ui_ParaExpForm import Ui_ParaExpForm
from CustomSimpleMethodListWidget import CustomSimpleMethodListWidget
from CustomParameterListWidget import CustomParameterListWidget

from SetParameterRangeDialog import SetParameterRangeDialog


class ParaExpForm(QWidget, Ui_ParaExpForm):
    def __init__(self, parent=None):
        super(ParaExpForm, self).__init__(parent)
        self.setupUi(self)

        # 成员变量
        self.method_list = MethodList()  # 方法列表(当前参数实验的模板)
        # 相关信息存储在method.json文件中，是字典
        self.method_data = json.load(open("config/method.json", "r", encoding="utf-8"))
        # 一个list，和可选参数列表里面的参数相对应，用于找到选择的参数在MethodList中的位置，
        # 每一个元素(X, Y)，X表示MethodList的第X个方法的第Y个参数(下标均从0开始)
        self.find_method_parameter = []
        self.select_index = 0
        # 保存用户选定的参数范围
        self.parameter_range = []
        # 保存用户选定的输出文件夹路径
        self.output_dir_path = ""

        # 在当前窗体加入自定义控件
        method_list_layout = QVBoxLayout(self.MethodList)
        method_list_layout.setContentsMargins(0, 0, 0, 0)
        self.custom_method_list = CustomSimpleMethodListWidget()
        method_list_layout.addWidget(self.custom_method_list)

        parameter_list_layout = QVBoxLayout(self.ParameterList)
        parameter_list_layout.setContentsMargins(0, 0, 0, 0)
        self.custom_parameter_list = CustomParameterListWidget()
        parameter_list_layout.addWidget(self.custom_parameter_list)

        # 设置当前窗口控件的初始状态
        self.btnSelectParameter.setEnabled(False)  # 锁定”选定该参数“按钮，正确打开工程后解锁
        self.btnSetOutputFolder.setEnabled(False)  # 锁定”设定输出文件夹“按钮，正确选择参数后解锁
        self.btnRunParaExp.setEnabled(False)  # 锁定”批量运行“按钮，正确设定输出文件夹后解锁

        self.lblCurrentProject.setText("未选择工程")
        self.lblMethodNumber.setText("")
        self.lblParameterNumber.setText("")
        self.lblSelectParameter.setText("未选择参数")
        self.lblParameterRange.setText("")
        self.lblExpNumber.setText("")

        # 绑定槽和函数
        self.btnOpenProject.clicked.connect(self.open_project)
        self.btnSelectParameter.clicked.connect(self.select_parameter)
        self.btnSetOutputFolder.clicked.connect(self.set_output_folder)
        self.btnRunParaExp.clicked.connect(self.run_parameter_exp)

        SetParameterRangeDialog.global_signal.update_text.connect(self.show_status)
        SetParameterRangeDialog.global_signal.set_parameter_range.connect(self.set_parameter_range)

    def open_project(self):
        """
        ”打开工程“按钮的槽函数
        :return:
        """
        # 弹出对话框选择工程文件路径
        project_path, project_type = QFileDialog.getOpenFileName(self, "选择工程文件", filter="Project Files(*.prj)")
        if project_path == "":
            return
        self.method_list.load_project(project_path)  # 从工程文件中读取信息

        # 将读取的信息进行解析，显示在界面上
        # 显示前，先清空当前列表里面的内容
        self.custom_method_list.clear_list_item()
        self.custom_parameter_list.clear_list_item()
        self.find_method_parameter.clear()
        # 如果不包含方法
        if self.method_list.project_data["method_num"] == 0:
            self.show_status("选定工程包含不包含方法，请重新选定。")
            return
        # 如果包含方法
        else:
            self.lblCurrentProject.setText(self.method_list.project_data["name"])
            parameter_num = 0
            # 遍历方法列表，同时生成方法列表和参数列表显示在界面上
            for method_index, method in enumerate(self.method_list.project_data["methods"]):
                # 如果该方法不包含参数，不生成参数列表字符串
                if not method["parameters"]:
                    self.custom_method_list.add_list_item(method["name"], "None")
                # 如果该方法包含参数，生成一个字符串
                else:
                    self.lblMethodNumber.setText(str(self.method_list.project_data["method_num"]))
                    parameters_str = ""
                    parameter_data = self.method_data[method["type_index"]][method["method_index"]]["parameters"]
                    for parameter_index, parameter in enumerate(method["parameters"]):
                        parameters_str += "{} = {} ".format(parameter["name"], parameter["value"])
                        # 查找对应方法的参数限定
                        constraints_str = "{}, max: {}, min: {}, {}".format(
                            parameter_data[parameter_index]["type"],
                            parameter_data[parameter_index]["max"],
                            parameter_data[parameter_index]["min"],
                            parameter_data[parameter_index]["constraints"]
                        )
                        self.custom_parameter_list.add_list_item(method["name"],
                                                                 parameter["name"],
                                                                 constraints_str)
                        self.find_method_parameter.append((method_index, parameter_index))
                        parameter_num += 1
                    # 生成参数字符串后，显示在方法列表中
                    self.custom_method_list.add_list_item(method["name"], parameters_str)
            if parameter_num == 0:
                self.show_status("选定工程包含不包含任何可选参数，请重新选定。")
                return
            self.lblParameterNumber.setText(str(parameter_num))
            self.show_status("已选定工程：{} \n路径：{}".format(self.method_list.project_data["name"],
                                                       project_path))
            self.btnSelectParameter.setEnabled(True)  # 解锁”选定该参数“按钮
            self.custom_parameter_list.tableWidget.selectRow(0)  # 默认选择第1个参数

    def select_parameter(self):
        """
        按钮”选定该参数“的槽函数
        :return:
        """
        # 获取相关参数的信息
        index = self.custom_parameter_list.tableWidget.currentRow()  # 当前选中行的行号，表示用户选择了哪一个参数，默认是0
        self.select_index = index
        method_index, parameter_index = self.find_method_parameter[index]  # 从成员变量中查到是第几个方法的第几个参数
        # 从method_list中根据下标找到方法的名字，在根据下标找到参数的名字
        method_name = self.method_list.project_data["methods"][method_index]["name"]
        parameter_name = self.method_list.project_data["methods"][method_index]["parameters"][parameter_index]["name"]
        # 显示在界面上
        self.show_status("选定了【步骤{} {}】的【参数{} {}】".format(method_index + 1,
                                                         method_name,
                                                         parameter_index + 1,
                                                         parameter_name))
        self.lblSelectParameter.setText("步骤{} 参数 {}".format(method_index + 1,
                                                            parameter_name))
        # 弹出对话框，传入方法信息和参数信息，显示对话框
        method_info = self.method_list.project_data["methods"][method_index]  # 从method_list中找到的方法信息
        # 从method_data配置信息中找到参数信息
        parameter_info \
            = self.method_data[method_info["type_index"]][method_info["method_index"]]["parameters"][parameter_index]
        dialog = SetParameterRangeDialog(method_info, parameter_info, parent=self)
        dialog.show()

    def show_status(self, message: str):
        """
        自定义信号update_text的槽函数，属于Ui线程，根据传入字符串信息，添加到末尾
        :param message: 字符串信息
        :return:
        """
        self.txtConsole.append(message)
        self.txtConsole.ensureCursorVisible()  # 保持光标一直可见，即保持在最后一行

    def set_parameter_range(self, parameter_range: list):
        """
        自定义信号set_parameter_range的槽函数，用于接收从SetParameterRangeDialog发出的信号
        :return:
        """
        # 显示相关信息
        self.show_status("实验参数列表：\n{}\n包含实验数量：{}".format(parameter_range, len(parameter_range)))
        self.lblParameterRange.setText(str(parameter_range))
        self.lblExpNumber.setText(str(len(parameter_range)))
        self.parameter_range = parameter_range
        self.btnSetOutputFolder.setEnabled(True)  # 解锁“设定输出文件夹”按钮

    def set_output_folder(self):
        """
        按钮“设定输出文件夹”的槽函数，设置参数实验生成的若干工程文件的输出文件夹
        :return:
        """
        output_dir_path = QFileDialog.getExistingDirectory(self, "选择生成工程文件输出文件夹")
        if output_dir_path == "":
            return
        self.output_dir_path = output_dir_path
        self.show_status("选定工程文件输出文件夹：{}".format(output_dir_path))
        self.btnRunParaExp.setEnabled(True)  # 解锁"批量运行"按钮

    def run_parameter_exp(self):
        """
        按钮“批量运行”的槽函数，
        1.根据设定的参数范围和输出文件夹，新建若干MethodList对象
        2.依次执行MethodList的方法，弹出窗口显示运行结果
        :return:
        """
        self.show_status("开始进行参数实验...")
        # 创建若干MethodList对象
        parameter_exp_list = []
        method_index, parameter_index = self.find_method_parameter[self.select_index]  # 从成员变量中查到是第几个方法的第几个参数
        method_name = self.method_list.project_data["name"]
        for index, parameter in enumerate(self.parameter_range):
            exp_method_list = copy.deepcopy(self.method_list)  # 复制一份MethodList对象（需要深拷贝）
            # 对复制得到的MethodList，修改对应的参数
            exp_method_list.project_data["methods"][method_index]["parameters"][parameter_index]["value"] = parameter
            exp_path = "{}/{}_{}.prj".format(self.output_dir_path, method_name, index + 1)
            exp_method_list.project_data["path"] = exp_path
            # 在指定文件夹保存对应的MethodList
            exp_method_list.save_project(exp_path)
            # 将修改过的MethodList添加到列表中
            parameter_exp_list.append(exp_method_list)
            self.show_status("生成工程文件：{}".format(exp_path))
        # TODO: 依次运行parameter_exp_list中的MethodList对象，显示运行结果
